/*
 * Copyright 2015 <copyright holder> <email>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include "es_socket.h"
#if defined(__ESVR_WIN32__)
    #define UNIFORM_V2CC(data) static_cast<const char*>(data)
    #define UNIFORM_V2C(data) static_cast<char*>(data)
    #include <winsock.h>
#elif defined(__ESVR_LINUX__)
    #define UNIFORM_V2CC(data) data
    #define UNIFORM_V2C(data) data
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <arpa/inet.h>
#endif

#if defined(__VER_LINUX__) || defined(__MINGW32__) || defined(__GNUC__)
#include <unistd.h>
#endif

#include <fcntl.h>
#include "es_log.h"


namespace esvr
{
    socket_t es_init_svr(const char* ip, uint16_t port) noexcept
    {
        socket_t fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(fd < 0)
        {
            LOG_FATAL("in function:%s line:%d:, cann't create socket.errno:%d", __FUNCTION__, __LINE__, ERRNO);
            return sock_t_invalid;
        }
        else
        {
            LOG_DEBUG("%s socket:%d", __FUNCTION__, fd);
        }
        int opt = 1;
        es_setopt(fd, SO_REUSEADDR, &opt, sizeof(opt));
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        addr.sin_port = htons(port);
        if(bind(fd, (sockaddr*)&addr, sizeof(sockaddr_in)))
        {
            LOG_FATAL("in function:%s line:%d bind error.errno:%d", __FUNCTION__, __LINE__, ERRNO);
            return sock_t_invalid;
        }
        else
        {
            LOG_DEBUG("bind success.socket:%d, port:%d", fd, port);
        }
        return fd;
    }

    socket_t es_init_cli() noexcept
    {
        socket_t fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(fd < 0)
        {
            LOG_FATAL("in function:%s line:%d:, cann't create socket.errno:%d", __FUNCTION__, __LINE__, ERRNO);
            return sock_t_invalid;
        }
        else
        {
            LOG_DEBUG("%s socket:%d", __FUNCTION__, fd);
        }
        return fd;
    }

    int es_listen(socket_t fd) noexcept
    {
        int ret = listen(fd, 1024);
        if(ret)
        {
            LOG_FATAL("in function:%s line:%d bind error.errno:%d", __FUNCTION__, __LINE__, ERRNO);
        }
        else
        {
            LOG_DEBUG("listen success.");
        }
        return ret;
    }

    int es_connect(socket_t fd, const char* ip, uint16_t port) noexcept
    {
        struct sockaddr_in addr;
        #ifdef __ESVR_WIN32__
        addr.sin_addr.s_addr = inet_addr(ip);
        #else
        inet_pton(AF_INET, ip, (void*)&addr.sin_addr);
        #endif
        addr.sin_port = htons(port);
        addr.sin_family = AF_INET;
        int ret = connect(fd, (sockaddr*) &addr, sizeof(sockaddr_in));
        if(ret)
        {
            LOG_FATAL("in function:%s line:%d connect error.errno:%d", __FUNCTION__, __LINE__, ERRNO);
        }
        else
        {
            LOG_DEBUG("connect success.");
        }
        return ret;
    }

    void es_close(socket_t fd) noexcept
    {
        #ifdef __ESVR_WIN32__
        closesocket(fd);
        #elif defined(__ESVR_LINUX__)
        if(fd >= 0) close(fd);
        #endif
    }


    socket_t es_accept(socket_t svr_fd) noexcept
    {
        socket_t fd = accept(svr_fd, NULL, NULL);
        if(fd < 0)
        {
            LOG_FATAL("in function:%s line:%d:, cann't accept.errno:%d", __FUNCTION__, __LINE__, ERRNO);
            return sock_t_invalid;
        }
        return fd;
    }

    int es_send(socket_t fd, const char* buffer, const size_t len) noexcept
    {
        size_t send_counter= 0;
        while(send_counter < len)
        {
            size_t send_len =  send(fd, buffer + send_counter, len - send_counter, 0);
            if(len > 0) send_counter += send_len;
            else
            {
                LOG_FATAL("in function:%s line:%d:, send error.errno:%d", __FUNCTION__, __LINE__, ERRNO);
                return len;
            }
        }
        return 0;
    }

    int es_recv(socket_t fd, char* buffer, const size_t max_size) noexcept
    {
        int len = ::recv(fd, buffer, max_size, 0);
        return len;
    }

    void es_setopt(socket_t fd, int optname, const void* optval, socklen_t optlen) noexcept
    {
        ::setsockopt(fd, SOL_SOCKET, optname, UNIFORM_V2CC(optval), optlen);
    }

    void es_getopt(socket_t fd, int optname, void* optval, socklen_t* optlen) noexcept
    {
        ::getsockopt(fd, SOL_SOCKET, optname, UNIFORM_V2C(optval), optlen);
    }

    void es_fcntl(socket_t fd, int flags, bool open) noexcept
    {
        #ifdef __ESVR_WIN32__
        unsigned long mode = open;
        ioctlsocket(fd, FIONBIO, &mode);
        #elif defined(__ESVR_LINUX__)
        int old_flag = ::fcntl(fd, F_GETFL, 0);
        if(old_flag < 0)
        {
            LOG_ERROR("in function:%s, line:%d, error:%d", __FUNCTION__, __LINE__, old_flag);
        }
        else
        {
            if(open)
            {
                flags |= old_flag;
                fcntl(fd, F_SETFL, flags);
            }
            else
            {
                old_flag &= ~flags;
                fcntl(fd, F_SETFL, old_flag);
            }
        }
        #endif
    }

    void es_init_sock_module() noexcept
    {
        #ifdef __ESVR_WIN32__
        WSADATA  Ws;
        if(WSAStartup(MAKEWORD(2,2), &Ws) != 0)
        {
            LOG_ERROR("in function:%s, line:%d, error:%d", __FUNCTION__, __LINE__, ERRNO);
        }
        #endif
    }

    void es_destroy_sock_module() noexcept
    {
        #ifdef __ESVR_WIN32__
        WSACleanup();
        #endif
    }
};



